<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>贪吃蛇</title>
  <style>
    body {
      background-color: #eee;
    }

    .container {
      text-align: center;
    }

    .top {
      margin: 20px auto;
      width: 640px;
    }

    #score {
      float: left;
    }

    .main {
      position: absolute;
      left: 50%;
      transform: translateX(-50%);
      width: 642px;
      height: 402px;
    }

    #snake {
      border: 1px solid #000;
      width: 640px;
      height: 400px;
      display: inline-block;
      z-index: 99;
      background-color: rgba(0, 0, 0, .1);
    }

    #mask {
      background-color: rgba(0, 0, 0, .5);
      position: absolute;
      left: 0;
      top: 0;
      width: 100%;
      height: 100%;
      z-index: 100;
      display: block;
      color: #fff;
      line-height: 400px;
      text-align: center;
      font-size: 30px;
      cursor: pointer;
    }
  </style>
</head>

<body>
  <div class="container">
    <div class="top">
      <span id="score">Score: 0</span>
      <button id="restart">重新开始</button>
      <button id="stop">暂停</button>
      <button id="continue">继续</button>
    </div>
    <div class="main">
      <canvas id="snake" width="640" height="400"></canvas>
      <div id="mask">开始</div>
    </div>
  </div>

  <script>
    let greedySnake = null
    let score = document.querySelector('#score')
    let restart = document.querySelector('#restart')
    let stop = document.querySelector('#stop')
    let conti = document.querySelector('#continue')
    let mask = document.querySelector('#mask')

    restart.onclick = () => {
      if (!greedySnake.isStart) return
      greedySnake.start()
    }
    stop.onclick = () => {
      if (greedySnake.isStop || !greedySnake.isStart) return
      greedySnake.stop()
    }
    conti.onclick = () => {
      if (!greedySnake.isStop || !greedySnake.isStart) return
      greedySnake.continue()
    }
    mask.onclick = () => {
      if (!greedySnake.isStart) {
        greedySnake.start()
      } else {
        greedySnake.continue()
      }
    }

    // 大小为64 * 40
    class GreedySnake {
      constructor() {
        this.canvas = document.querySelector('#snake')
        this.ctx = this.canvas.getContext('2d')
        this.maxX = 64          // 最大行
        this.maxY = 40          // 最大列
        this.itemWidth = 10     // 每个点的大小
        this.direction = 'right'// up down right left 方向
        this.speed = 150        // ms 速度
        this.isStop = false     // 是否暂停
        this.isOver = false     // 是否结束
        this.isStart = false    // 是否开始
        this.score = 0          // 分数
        this.timer = null       // 移动定时器
        this.j = 1
        this.canChange = true

        this.grid = new Array()

        for (let i = 0; i < this.maxX; i++) {
          for (let j = 0; j < this.maxY; j++) {
            this.grid.push([i, j])
          }
        }

        this.drawGridLine()
        this.getDirection()
      }

      // 开始
      start() {
        if (this.timer) {
          clearTimeout(this.timer)
        }
        if (!this.isStart) {
          this.isStart = true
        }
        this.score = 0
        this.speed = 150
        this.isStop = false
        this.isOver = false
        this.direction = 'right'
        this.createSnake()
        this.createFood()
        this.draw()
        this.move()
        mask.style.display = 'none'
      }

      // 创建蛇主体
      createSnake() {
        this.snake = [
          [4, 25],
          [3, 25],
          [2, 25],
          [1, 25],
          [0, 25]
        ]
      }

      // 移动
      move() {
        if (this.isStop) return

        let [x, y] = this.snake[0]
        switch (this.direction) {
          case 'left':
            x--
            break
          case 'right':
            x++
            break
          case 'up':
            y--
            break
          case 'down':
            y++
            break
        }

        // 如果下一步不是食物的位置
        if (x !== this.food[0] || y !== this.food[1]) {
          this.snake.pop()
        } else {
          this.createFood()
        }

        if (this.over([x, y])) {
          this.isOver = true
          mask.style.display = 'block'
          mask.innerHTML = '结束'
          return
        }
        if (this.completed()) {
          mask.style.display = 'block'
          mask.innerHTML = '恭喜您，游戏通关'
          return
        }

        this.snake.unshift([x, y])

        this.draw()
        this.canChange = true
        this.timer = setTimeout(() => this.move(), this.speed)
      }

      // 暂停游戏
      stop() {
        if (this.isOver) return
        this.isStop = true
        mask.style.display = 'block'
        mask.innerHTML = '暂停'
      }

      // 继续游戏
      continue() {
        if (this.isOver) return
        this.isStop = false
        this.move()
        mask.style.display = 'none'
      }

      getDirection() {
        // 上38 下40 左37 右39 不能往相反的方向走
        document.onkeydown = (e) => {
          // 在贪吃蛇移动的间隔内不能连续改变两次方向
          if (!this.canChange) return
          switch (e.keyCode) {
            case 37:
              if (this.direction !== 'right') {
                this.direction = 'left'
                this.canChange = false
              }
              break
            case 38:
              if (this.direction !== 'down') {
                this.direction = 'up'
                this.canChange = false
              }
              break
            case 39:
              if (this.direction !== 'left') {
                this.direction = 'right'
                this.canChange = false
              }
              break
            case 40:
              if (this.direction !== 'up') {
                this.direction = 'down'
                this.canChange = false
              }
              break
            case 32:
              // 空格暂停与继续
              if (!this.isStop) {
                this.stop()
              } else {
                this.continue()
              }
              break
          }
        }
      }
      createPos() {
        let [x, y] = this.grid[(Math.random() * this.grid.length) | 0]

        for (let i = 0; i < this.snake.length; i++) {
          if (this.snake[i][0] == x && this.snake[i][1] == y) {
            return this.createPos()
          }
        }

        return [x, y]
      }
      // 生成食物
      createFood() {
        this.food = this.createPos()

        // 更新分数
        score.innerHTML = 'Score: ' + this.score++

        if (this.speed > 50) {
          this.speed--
        }
      }

      // 结束
      over([x, y]) {
        if (x < 0 || x >= this.maxX || y < 0 || y >= this.maxY) {
          return true
        }

        if (this.snake.some(v => v[0] === x && v[1] === y)) {
          return true
        }
      }

      // 完成
      completed() {
        if (this.snake.length == this.maxX * this.maxY) {
          return true
        }
      }

      // 网格线
      drawGridLine() {
        for (let i = 1; i < this.maxY; i++) {
          this.ctx.moveTo(0, i * this.itemWidth)
          this.ctx.lineTo(this.canvas.width, i * this.itemWidth)
        }

        for (let i = 1; i < this.maxX; i++) {
          this.ctx.moveTo(i * this.itemWidth, 0)
          this.ctx.lineTo(i * this.itemWidth, this.canvas.height)
        }
        this.ctx.lineWidth = 1
        this.ctx.strokeStyle = '#ddd'
        this.ctx.stroke()
      }

      // 绘制
      draw() {
        // 清空画布
        this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height)

        this.drawGridLine()

        this.ctx.fillStyle = "#000"
        this.ctx.fillRect(
          this.food[0] * this.itemWidth + this.j,
          this.food[1] * this.itemWidth + this.j,
          this.itemWidth - this.j * 2,
          this.itemWidth - + this.j * 2
        )
        this.j ^= 1

        this.ctx.fillStyle = "green"
        this.ctx.fillRect(
          this.snake[0][0] * this.itemWidth + 0.5,
          this.snake[0][1] * this.itemWidth + 0.5,
          this.itemWidth - 1,
          this.itemWidth - 1
        )
        this.ctx.fillStyle = "red"
        for (let i = 1; i < this.snake.length; i++) {
          this.ctx.fillRect(
            this.snake[i][0] * this.itemWidth + 0.5,
            this.snake[i][1] * this.itemWidth + 0.5,
            this.itemWidth - 1,
            this.itemWidth - 1
          )
        }
      }
    }
    greedySnake = new GreedySnake()
  </script>
</body>

</html>